package com.zoe.onelink.feign.decode;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.TypeUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zoe.onelink.annotation.feign.AutoThrowEx;
import com.zoe.onelink.common.enumeration.ExceptionCodeEnum;
import com.zoe.onelink.common.exception.ApiException;
import com.zoe.onelink.common.model.ApiResult;
import com.zoe.onelink.common.model.IResult;
import com.zoe.onelink.feign.util.FeignDecoderUtil;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>标题: Feign返回结果解析器</p>
 * <p>描述: 该解析器只支持ApiResult类型的返回结果,用来限制框架统一内部调用风格</p>
 * <p>版权: Copyright (c) 2021</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2021-12-16
 */
@Slf4j
@RequiredArgsConstructor
public class FeignApiResultDecoder implements Decoder {

    private final ObjectMapper objectMapper;

    /**
     * Key: Client全路径类名 , Value: true/false
     */
    private final Map<String, Boolean> AUTO_THROW_EX_CACHE = new ConcurrentHashMap<>();

    @Override
    public Object decode(Response response, Type type) throws IOException, FeignException {

        // 检查返回类型
        if (!(type instanceof ParameterizedType) || ((ParameterizedType) type).getRawType() != ApiResult.class) {
            this.closeResponse(response);
            throw ApiException.createEx(ExceptionCodeEnum.INCORRECT_INNER_API_RESPONSE_TYPE, type.getTypeName());
        }
        String respText = IoUtil.read(response.body().asReader(StandardCharsets.UTF_8));

        ApiResult<?> apiResult = this.objectMapper.readValue(respText, new TypeReference<>() {
            @Override
            public Type getType() {
                return type;
            }
        });
        // 检查是否可以抛出异常
        if (this.shouldAutoThrow(response, apiResult)) {
            throw FeignDecoderUtil.createInnerApiEx(response, apiResult);
        }
        Object data = apiResult.getData();
        Object result;
        // FeignClient方法返回结果为IResult
        if (IResult.class.isAssignableFrom(TypeUtil.getClass(type))) {
            result = apiResult;
            // 返回数据为对象类型
        } else if (data instanceof JSONObject) {
            result = ((JSONObject) data).toJavaObject(type);
            // 返回数据类型为数组
        } else if (data instanceof JSONArray) {
            result = data;
        } else {
            result = data;
        }
        return result;

    }

    /**
     * 关闭响应流
     */
    private void closeResponse(Response response) {
        try {
            response.close();
        } catch (Exception e) {
            log.warn("关闭Feign响应流失败", e);
        }
    }

    /**
     * 是否自动抛出异常
     */
    private boolean shouldAutoThrow(Response response, ApiResult<?> apiResult) {
        Class<?> clientClass = response.request().requestTemplate().feignTarget().type();
        Boolean isAutoThrow = AUTO_THROW_EX_CACHE.computeIfAbsent(clientClass.getName(),
                className -> AnnotationUtils.findAnnotation(clientClass, AutoThrowEx.class) != null);
        return Boolean.TRUE.equals(isAutoThrow) && !apiResult.checkSuccess();
    }
}
